#include "utils.h"
#include "state.h"
#include "draw.h"
#include "item.h"
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <clocale>
#include <algorithm>
#include <iostream>
#include <X11/Xlib.h>

// Safe calloc wrapper (checks for overflow to prevent memory corruption)
void *ecalloc(size_t nmemb, size_t size)
{
    if (size != 0 && nmemb > SIZE_MAX / size)
    {
        errno = ENOMEM;
        fprintf(stderr, "ecalloc: memory overflow\n");
        return nullptr;
    }
    void *ptr = calloc(nmemb, size);
    if (!ptr)
    {
        fprintf(stderr, "ecalloc: failed to allocate memory (%s)\n", strerror(errno));
    }
    return ptr;
}

// UTF-8 decoder (converts 1-4 byte UTF-8 sequence to Unicode code point)
int utf8Decode(const char *s_in, long *out_char, int *out_err)
{
    const unsigned char *s = reinterpret_cast<const unsigned char *>(s_in);
    *out_err = 0;
    *out_char = 0;

    if (*s == 0)
        return 0; // End of string

    // 1-byte sequence (ASCII)
    if ((s[0] & 0x80) == 0)
    {
        *out_char = s[0];
        return 1;
    }

    // 2-byte sequence
    if ((s[0] & 0xE0) == 0xC0)
    {
        if ((s[1] & 0xC0) != 0x80)
        {
            *out_err = 1;
            return 1;
        }
        *out_char = ((s[0] & 0x1F) << 6) | (s[1] & 0x3F);
        return 2;
    }

    // 3-byte sequence
    if ((s[0] & 0xF0) == 0xE0)
    {
        if ((s[1] & 0xC0) != 0x80 || (s[2] & 0xC0) != 0x80)
        {
            *out_err = 1;
            return 1;
        }
        *out_char = ((s[0] & 0x0F) << 12) | ((s[1] & 0x3F) << 6) | (s[2] & 0x3F);
        return 3;
    }

    // 4-byte sequence
    if ((s[0] & 0xF8) == 0xF0)
    {
        if ((s[1] & 0xC0) != 0x80 || (s[2] & 0xC0) != 0x80 || (s[3] & 0xC0) != 0x80)
        {
            *out_err = 1;
            return 1;
        }
        *out_char = ((s[0] & 0x07) << 18) | ((s[1] & 0x3F) << 12) | ((s[2] & 0x3F) << 6) | (s[3] & 0x3F);
        return 4;
    }

    // Invalid UTF-8 sequence
    *out_err = 1;
    return 1;
}

// UTF-8 aware cursor movement (returns new cursor position)
size_t nextrune(size_t pos, int dir)
{
    State &state = getState();
    const std::string &text = state.text;
    size_t len = text.size();

    if (dir > 0)
    { // Move right
        if (pos >= len)
            return pos;
        int err;
        long c;
        size_t bytes = utf8Decode(text.c_str() + pos, &c, &err);
        return pos + bytes;
    }
    else
    { // Move left
        if (pos == 0)
            return 0;
        size_t newPos = pos - 1;
        while (newPos > 0 && (static_cast<unsigned char>(text[newPos]) & 0xC0) == 0x80)
        {
            newPos--;
        }
        return newPos;
    }
}

// Read items from stdin (one item per line)
void readStdin()
{
    State &state = getState();
    Item *lastItem = nullptr;
    std::string line;

    // Read all lines from stdin
    while (std::getline(std::cin, line))
    {
        // Remove trailing newline (if present)
        if (!line.empty() && line.back() == '\n')
        {
            line.pop_back();
        }
        // Create new item and add to linked list
        Item *item = new Item(line);
        appendItem(item, &state.items, &lastItem);
    }

    // Initialize filtered matches to all items
    state.matches = state.items;
    state.matchend = lastItem;
    state.sel = state.matches; // Select first item by default
}

// Filter items to match user input (case-insensitive if enabled)
void filterMatches()
{
    State &state = getState();
    if (!state.items)
        return;

    // Clear previous matches
    state.matches = nullptr;
    state.matchend = nullptr;

    std::string input = state.text;
    // Convert input to lowercase for case-insensitive matching
    if (state.cfg.case_insensitive)
    {
        std::transform(input.begin(), input.end(), input.begin(), ::tolower);
    }

    // Iterate all items and check for match
    Item *currItem = state.items;
    while (currItem)
    {
        std::string itemText = currItem->text;
        if (state.cfg.case_insensitive)
        {
            std::transform(itemText.begin(), itemText.end(), itemText.begin(), ::tolower);
        }
        // Include item if input is empty or is a substring
        if (input.empty() || itemText.find(input) != std::string::npos)
        {
            appendItem(currItem, &state.matches, &state.matchend);
        }
        currItem = currItem->right;
    }

    // Reset selection to first matched item
    state.sel = state.matches;
    state.curr = state.matches;
    calcOffsets(); // Recalculate page offsets
}

// Calculate visible page boundaries (prev/curr/next pages)
void calcOffsets()
{
    State &state = getState();
    if (!state.matches || state.cfg.lines <= 0)
    {
        state.prev = nullptr;
        state.next = nullptr;
        return;
    }

    // Calculate total number of items
    int totalItems = 0;
    Item *currItem = state.matches;
    while (currItem)
    {
        totalItems++;
        currItem = currItem->right;
    }

    // Find current page index
    int currPage = 0;
    int itemIndex = 0;
    currItem = state.matches;
    while (currItem && currItem != state.curr)
    {
        itemIndex++;
        if (itemIndex % state.cfg.lines == 0)
        {
            currPage++;
        }
        currItem = currItem->right;
    }

    // Calculate previous page (if exists)
    state.prev = nullptr;
    if (currPage > 0)
    {
        int prevPageStart = (currPage - 1) * state.cfg.lines;
        currItem = state.matches;
        for (int i = 0; i < prevPageStart && currItem; ++i)
        {
            if (i == prevPageStart)
            {
                state.prev = currItem;
                break;
            }
            currItem = currItem->right;
        }
    }

    // Calculate next page (if exists)
    state.next = nullptr;
    int nextPageStart = (currPage + 1) * state.cfg.lines;
    if (nextPageStart < totalItems)
    {
        currItem = state.matches;
        for (int i = 0; i < nextPageStart && currItem; ++i)
        {
            if (i == nextPageStart)
            {
                state.next = currItem;
                break;
            }
            currItem = currItem->right;
        }
    }
}

// Cleanup all allocated resources
void cleanup()
{
    State &state = getState();
    if (!state.dpy)
        return;

    // Free menu items linked list
    Item *currItem = state.items;
    while (currItem)
    {
        Item *next = currItem->right;
        delete currItem;
        currItem = next;
    }

    // Cleanup X11 resources
    if (state.xic)
    {
        XDestroyIC(state.xic);
        // TODO: type mismatch
        // XCloseIM(state.xic); // Close input method
    }
    if (state.win != None)
    {
        XDestroyWindow(state.dpy, state.win);
    }
    // Drw resources are cleaned up by its destructor
    XCloseDisplay(state.dpy);
}
